/*
 * Copyright (c) 2010 Vadim Zhukov <persgray@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef OW_PEER_H
#define OW_PEER_H

enum { MAX_PEER_DATA = 8192 };		/* Max peer message data payload */


/*
 * Peer protocol.
 *
 * On the first connection client should determine initial session number.
 * To do this he sets "session" to zero.
 *
 * The collector should ask colleagues (and check himself, of course) about last
 * session number they had with that peer, add CLIENT_SAFE_SESSION_STEP to this
 * number and send to client. On each new peer connection made client will
 * increment session number by one. When collector receive message from monitor
 * with pr_session bigger than collector seen before, he should use this new
 * session value.
 *
 * When sequence number is about to be wrapped then session should be
 * incremented and sequence number reset to 0.
 *
 * All numeric fields are in network (big-endian) byte order.
 *
 * All the text lines (log lines, names, etc.) sent on the wire are not
 * null-terminated.
 *
 * All structures that do not end with text should be aligned to 64 bits.
 *
 * Timestamp conversion to/from time_t should be done via timestamp_pack() and
 * timestamp_unpack() from timestamp.h.
 */

enum { CLIENT_SAFE_SESSION_STEP = 100 };

union peer_connector {
#ifndef NO_SSL
	struct assl_context	*sslctx;
#endif
	int			 fd;
};

#ifndef NO_SSL
struct ssl_paths {
	char	ca_crt[PATH_MAX];
	char	my_crt[PATH_MAX];
	char	my_key[PATH_MAX];
};
#endif

enum { PEER_MAX_QUEUE_LENGTH = 1000 };

enum {
	PEER_QUEUE_OUTPUT,
	PEER_QUEUE_INPUT,
	PEER_QUEUE_COUNT
};

TAILQ_HEAD(peer_list, peer);
struct peer {
	TAILQ_ENTRY(peer)	 entry;
	union peer_connector	 conn;
	struct sockaddr_storage	 addr;
	struct pollfd		*pollptr;
	struct peer_task_queue	 tasks[PEER_QUEUE_COUNT];
	struct peer_task	*incoming;     /* internal use */
#ifndef NO_SSL
	const struct ssl_paths	*ssl_paths;
#endif
	size_t			 qlength[PEER_QUEUE_COUNT];
	socklen_t		 addrlen;
	uint32_t		 session;
	uint32_t		 lastseq;
	int			 af;
	int			 status;
	uint			 flags;
	char			**trans_cmd;

	/* Standard errno number. Used in FAILED state only. */
	int			 fail_reason;

	/* Used only by collector now */
	char			 name[MAXHOSTNAMELEN];
};

enum {
	/*
	 * Indicates there are either fully received or sent tasks.
	 * Should be reset after check. See peer_has_completed_tasks().
	 */
	PEER_FLAG_HAS_COMPLETED_TASKS =	0x01,
#ifndef NO_SSL
	/*
	 * Set in peer_new() and should not be changed anymore.
	 * See peer_with_ssl().
	 */
	PEER_FLAG_WITH_SSL =		0x02,
#endif
};

enum {
	PEER_STATUS_NEW,		/* peer object just created or reset */
	PEER_STATUS_CONNECTED,		/* socket connected */
	PEER_STATUS_HELLO_SENT,		/* HELLO command sent, waiting OK */
	PEER_STATUS_SESSION,		/* HELLO-OK received, operating */
	PEER_STATUS_FAILED,		/* failure; connection is dropped */
	PEER_STATUS_UNVERIFIED,		/* used in peer_update_connections() */
	PEER_STATUS_COUNT
};

enum peer_connect_mode {
	PEER_CONN_PLAIN,
	PEER_CONN_TRANSPORT,
#ifndef NO_SSL
	PEER_CONN_SSL,
#endif
};

#define compare_peers(p1, p2) \
	((p1)->af - (p2)->af || \
	    (p1)->addrlen - (p2)->addrlen || \
	    memcmp(&(p1)->addr, &(p2)->addr, (p1)->addrlen))

#define peer_busy(peer) \
	(!SIMPLEQ_EMPTY(&(peer)->tasks[PEER_QUEUE_OUTPUT]))

#define peer_connected(peer)	\
	((peer)->status >= PEER_STATUS_CONNECTED && \
	    (peer)->status <= PEER_STATUS_SESSION)

#define peer_session_established(peer) \
	((peer)->status == PEER_STATUS_SESSION)

#define peer_hello_sent(peer) \
	((peer)->status == PEER_STATUS_HELLO_SENT)

#ifdef NO_SSL
#define peer_get_fd(peer) \
	((peer)->conn.fd)
#define peer_with_ssl(peer)	(0)
#else
#define peer_with_ssl(peer) \
	((peer)->flags & PEER_FLAG_WITH_SSL)

#define peer_get_fd(peer) \
	(peer_with_ssl(peer) ? (peer)->conn.sslctx->as_sock : (peer)->conn.fd)
#endif

enum {
	PEER_TH_CB_ERR      = -1, /* Processing failed, keep task in queue    */
	                          /* (the peer_handle() will abort execution) */
	PEER_TH_CB_FREE     =  0, /* Task processed and can be freed          */
	PEER_TH_CB_CARED    =  1, /* Task moved on (forwarded), do not free   */
	PEER_TH_CB_RESTART  =  2, /* Task should be restarted, only on output */
};

enum {
	PEER_TH_F_SUCCESS = 0x01,
	PEER_TH_F_FAIL    = 0x02,
};

struct peer_task_handler {
	 /* Should return on of the PEER_TH_CB_* values */
	int	(*func)(struct peer *, struct peer_task *);

	int	queue;
	int	cmdcode;	/* Set to PEER_CMD_UNKNOWN to handle all cmds */
	int	flags;
};


struct peer		*peer_new(enum peer_connect_mode, const char * const *);
void			 peer_free(struct peer *);
int			 peer_connect(struct peer *);
int			 peer_accept(struct peer *, int);
int			 peer_start_session(struct peer *, uint32_t,
    const char *);
int			 peer_close(struct peer *);
int			 peer_is_closed(struct peer *);
int			 peer_has_completed_tasks(struct peer *);
void			 peer_run_queues(struct peer *);
int			 peer_add_task(struct peer *, struct peer_task *);
struct peer_task	*peer_pick_done_task(struct peer *, int);
void			 peer_send_tasks_to_any(struct peer_task_queue *,
			    struct peer_list *);
int			 peer_handle(struct peer *, struct peer_task_handler *,
			    size_t);

#ifdef NO_SSL
int	 		 peer_update_connections(struct peer_list *,
			    struct addrinfo *, enum peer_connect_mode, int *,
			    const char * const *, void *);
#else
int	 		 peer_update_connections(struct peer_list *,
			    struct addrinfo *, enum peer_connect_mode, int *,
			    const char * const *, struct ssl_paths *);
#endif

const char		*peer_status_name(const struct peer *);

#endif /* OW_PEER_H */
